package controller.preprocessor;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.concurrent.Semaphore;

import javax.swing.Timer;

import controller.SessionManager;

import model.cnf.CNF;
import model.preprocessor.EPreProcessor;
import model.preprocessor.IllegalPreProcessorParameterException;
import model.preprocessor.PreProcessorParameter;
import model.preprocessor.PreProcessorResult;

/**
 * <p>
 * 
 * Represent a preprocessor. Override checkParameter() to put some limitation on the parameter that
 * can be use with your preprocessor, by default it just call the {@link PreProcessorParameter}
 * method to check parameter .
 * </p>
 * <p>
 * 
 * getCommandLine() return the list of parameter that will be use to run the preprocessor.
 * </p>
 * <p>
 * runPreProcessor() is use to call the preprocessor.
 * </p>
 * <p>
 * hasTimeOut() is to know if your preprocessor has some mechanism that allow it to stop itself
 * after a certain time, if not this method should return false.
 * </p>
 * <p>
 * 
 * evaluate() will evaluate the CNF using the method describe previously, it will get the parameter
 * and run the preprocessor. If the preprocessor can't deal with the timeout, it will start a timer
 * that will destroy the process when the timeout is over.
 * </p>
 * <p>
 * 
 * Don't forget to set set the attribute "running" at true when it is running, and use the attribute
 * "process" to store the process executing the preprocessor in order to allow the internal
 * stop/timeout mechanism to work.
 * </p>
 * <p>
 * /!\ you must release the semaphore after the lunching of the process. It is use for example be
 * sure that a {@link SessionManager} can't be closed before a preprocessor has been completely
 * lunched, in this case the session will be close but the preprocessor will still be running.
 * </p>
 * 
 * @author stephane
 * 
 */
public abstract class PreProcessor implements ActionListener {

	/**
	 * little delay that is add to the timer to make sure that the the timer doesn't fire before the
	 * solver has been executed during all the time it was supposed to be running
	 */
	public static final int	ADD_DELAY	= 200;

	protected String		path		= "";
	protected Timer			timer		= new Timer(PreProcessorParameter.DEFAULT_DELAY + ADD_DELAY, this);
	private boolean			stop		= false;
	private boolean			running		= false;
	private boolean			timeout		= false;
	protected Semaphore		sem			= new Semaphore(1);
	protected EPreProcessor	type		= EPreProcessor.UNKNOW;

	/**
	 * store the process in this attribute
	 */
	protected Process		process		= null;

	/*
	 * if the solver is running
	 * 
	 * @return
	 *//*
		 * public boolean isRunning() {
		 * 
		 * try { sem.acquire(); } catch (InterruptedException e) { // TODO Auto-generated catch
		 * block e.printStackTrace(); return false; } running;
		 * 
		 * 
		 * }
		 */

	/**
	 * Use it to modify the running attribute /!\ not protected against concurrent access.
	 * 
	 * @param running
	 *            true if it is running.
	 */
	protected void setRunning(boolean running) {
		this.running = running;
	}

	/**
	 * Return the type of the preprocessor.
	 * 
	 * @return the type of the preprocessor.
	 */
	public EPreProcessor getType() {
		return type;
	}

	/**
	 * If the solver has been stop /!\ not protected against concurrent access.
	 * 
	 * @return if the solver has been stop
	 */
	protected boolean hasBeenStop() {
		return stop;
	}

	/**
	 * If the solver has been stop by timeout /!\ not protected against concurrent access.
	 * 
	 * @return if the solver has been stop by timeout
	 */
	protected boolean hasBeenTimeout() {
		return timeout;
	}

	/**
	 * Return the path of the file associate to the preprocessor.
	 * 
	 * @return the path
	 */
	public String getPath() {
		return path;
	}

	/**
	 * @return the {@link PreProcessorParameter}.
	 */
	public abstract PreProcessorParameter getPreProcessorParameter();

	/**
	 * Determine if the preprocessor has a timeout mechanism that allow him to stop after a certain
	 * time.
	 * 
	 * @return if it has one
	 */
	public abstract boolean hasTimeout();

	/**
	 * Check if the parameter are good.
	 * 
	 * @return if they are all right.
	 */
	public abstract boolean checkParameter();

	/**
	 * Return the arguments to give the real preprocessor.
	 * 
	 * @param cnfFile
	 *            the cnf
	 * @return a array of String equivalent to the command line you would write on a terminal
	 */
	public abstract String[] getCommandLine(String cnfFile);

	/**
	 * Run the preprocessor with the specified arguments.
	 * 
	 * @param cmd
	 *            the command line that will be used to lunch the preprocessor.
	 * 
	 * @param fileOutput
	 *            the file were the CNF that result from the execution will be store.
	 * @param manager
	 *            the {@link Semaphore} that must be released when the process is lunched.
	 * @return the {@link PreProcessorResult}
	 */
	public abstract PreProcessorResult runPreProcessor(String[] cmd, String fileOutput, Semaphore manager);

	/**
	 * Evaluate a CNF.
	 * 
	 * @param cnf
	 *            cnf to evaluate
	 * @param param
	 *            parameter
	 * @return the result of the evaluation
	 * @throws IllegalPreProcessorParameterException
	 *             if the arguments are not correct.
	 */
	public PreProcessorResult evaluate(CNF cnf, Semaphore manager) throws IllegalPreProcessorParameterException {
		try
		{
			sem.acquire();
		} catch (InterruptedException e)
		{// pb acquire
			if (manager != null)
				manager.release();
			return null;
		}

		String[] cmdArray = null;
		File tempIn = null;

		try
		{
			if (cnf == null)
				throw new IllegalArgumentException();
			if (!checkParameter())
				throw new IllegalPreProcessorParameterException();

			// recupere un fichier avec la cnf
			tempIn = cnf.getCNFfile();// cnf.export(directory);
			if (tempIn == null)
				throw new IllegalArgumentException();

			cmdArray = getCommandLine(tempIn.getPath());
		} catch (IllegalPreProcessorParameterException e)
		{
			sem.release();
			if (manager != null)
				manager.release();
			throw e;
		} catch (IllegalArgumentException e)
		{
			sem.release();
			if (manager != null)
				manager.release();
			throw e;
		}

		stop = running = timeout = false;
		process = null;

		if (getPreProcessorParameter().needTimeOut() && !hasTimeout())
		{// gere
			// le
			// timeout
			// manuellement
			timer = new Timer(getPreProcessorParameter().getTimeOut() + ADD_DELAY, this);
			timer.start();
			System.out.println("demarre timer " + (getPreProcessorParameter().getTimeOut() + ADD_DELAY));
		}

		running = true;

		PreProcessorResult res = runPreProcessor(cmdArray, getPreProcessorParameter().getOut(), manager);

		running = false;
		// arrete timer
		timer.stop();
		// tempIn.delete();

		return res;
	}

	/**
	 * Substitute the " " with "\ " for command argument in linux
	 * 
	 * @param arg
	 *            the original argument
	 * @return the argument with the "\ " instead of the " "
	 */
	public static String correctFormatForLinux(String arg) {
		String res;
		if (arg.indexOf(' ') != -1)
			res = arg.substring(0, arg.indexOf(' ')) + "\\ " + arg.substring(arg.indexOf(' ') + 1);
		else
			res = arg;

		return res;
	}

	/**
	 * Stop the execution.
	 */
	public void stop() {
		System.out.println("appelle stop");

		timer.stop();

		try
		{
			sem.acquire();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
			return;
		}

		if (running && process != null)
		{// arrete
			System.out.println("arrete le proc cause appele de stop()");
			running = false;
			stop = true;
			process.destroy();
		}

		sem.release();
	}

	@Override
	public void actionPerformed(ActionEvent arg0) {

		if (arg0.getSource() == timer)
		{// doit terminer l'execution
			timer.stop();
			try
			{
				sem.acquire();
			} catch (InterruptedException e)
			{
				e.printStackTrace();
				return;
			}

			if (running && process != null)
			{// arrete
				System.out.println("arrete le proc cause timeout");
				running = false;
				timeout = true;
				process.destroy();
			}

			sem.release();
		}
	}

	@Override
	public String toString() {
		return type.getDescription();
	}

}
